﻿using System;
using System.Collections.Generic;
using System.IO;
using PickGold.Data.Hsts.General.Compression;
using PickGold.Data.Hsts.General.Mathematics;

namespace PickGold.Data.Hsts.General.Persist
{
	public class SingleIndexerPersist : IIndexerPersist<float>
	{
		public const byte VERSION = 40;

		private int GetMaxDigits(Func<int, object, float> values, int count, object state)
		{
			var maxDigits = 0;
			for (var i = 0; i < count; i++)
			{
				var value = values(i, state);
				var digits = MathUtils.GetDigits(value);
				if (digits < 0)
					return -1;

				if (digits > maxDigits)
					maxDigits = digits;
			}

			return maxDigits;
		}

		public void Store(BinaryWriter writer, Func<int, object, float> values, int count, object state)
		{
			writer.Write(VERSION);

			DeltaCompression.Helper helper = null;
			long[] array = null;
			int digits;

			try
			{
				digits = GetMaxDigits(values, count, state);
				if (digits >= 0)
				{
					helper = new DeltaCompression.Helper();
					array = new long[count];

					var koef = Math.Pow(10, digits);
					for (var i = 0; i < count; i++)
					{
						var value = values(i, state);
						var v = checked((long)Math.Round(value * koef));

						array[i] = v;
						helper.Add(v);
					}
				}
			}
			catch (OverflowException)
			{
				digits = -1;
			}

			writer.Write((sbyte)digits);
			if (digits >= 0)
				DeltaCompression.Compress(writer, array, 0, count, helper);
			else
			{
				for (var i = 0; i < count; i++)
					writer.Write(values(i, state));
			}
		}

		public void Load(BinaryReader reader, Action<int, object, float> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Ïnvalid SingleIndexerPersist version.");

			int digits = reader.ReadSByte();

			if (digits >= 0)
			{
				double koef = Math.Pow(10, digits);
				DeltaCompression.Decompress(reader, (idx, o, val) => values(idx, o, (float)Math.Round(val / koef, digits)), count, state);
			}
			else //native read
			{
				for (int i = 0; i < count; i++)
					values(i, state, reader.ReadSingle());
			}
		}
	}

	public class DoubleIndexerPersist : IIndexerPersist<double>
	{
		public const byte VERSION = 40;

		private int GetMaxDigits(Func<int, object, double> values, int count, object state)
		{
			int maxDigits = 0;
			for (int i = 0; i < count; i++)
			{
				double value = values(i, state);
				int digits = MathUtils.GetDigits(value);
				if (digits < 0)
					return -1;

				if (digits > maxDigits)
					maxDigits = digits;
			}

			return maxDigits;
		}

		public void Store(BinaryWriter writer, Func<int, object, double> values, int count, object state)
		{
			writer.Write(VERSION);

			DeltaCompression.Helper helper = null;
			long[] array = null;
			int digits;

			try
			{
				digits = GetMaxDigits(values, count, state);
				if (digits >= 0)
				{
					helper = new DeltaCompression.Helper();
					array = new long[count];

					var koef = Math.Pow(10, digits);
					for (int i = 0; i < count; i++)
					{
						var value = values(i, state);
						var v = checked((long)Math.Round(value * koef));

						array[i] = v;
						helper.Add(v);
					}
				}
			}
			catch (OverflowException)
			{
				digits = -1;
			}

			writer.Write((sbyte)digits);
			if (digits >= 0)
				DeltaCompression.Compress(writer, array, 0, count, helper);
			else
			{
				for (int i = 0; i < count; i++)
					writer.Write(values(i, state));
			}
		}

		public void Load(BinaryReader reader, Action<int, object, double> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid DoubleIndexerPersist version.");

			var digits = reader.ReadSByte();
			if (digits >= 0)
			{
				var koef = Math.Pow(10, digits);
				DeltaCompression.Decompress(reader, (idx, o, val) => values(idx, o, (double)Math.Round(val / koef, digits)), count, state);
			}
			else //native read
			{
				for (var i = 0; i < count; i++)
					values(i, state, reader.ReadDouble());
			}
		}
	}

	public class DecimalIndexerPersist : IIndexerPersist<decimal>
	{
		public const byte VERSION = 40;

		private int GetMaxDigits(Func<int, object, decimal> values, int count, object state)
		{
			int maxDigits = 0;
			for (int i = 0; i < count; i++)
			{
				decimal value = values(i, state);
				int digits = MathUtils.GetDigits(value);
				if (digits > maxDigits)
					maxDigits = digits;
			}

			return maxDigits;
		}

		#region IIndexerPersist<decimal> Members

		public void Store(BinaryWriter writer, Func<int, object, decimal> values, int count, object state)
		{
			writer.Write(VERSION);

			DeltaCompression.Helper helper = null;
			long[] array = null;
			int digits;

			try
			{
				digits = GetMaxDigits(values, count, state);
				if (digits <= 15)
				{
					helper = new DeltaCompression.Helper();
					array = new long[count];

					var koef = (decimal)Math.Pow(10, digits);
					for (var i = 0; i < count; i++)
					{
						var value = values(i, state);
						var v = checked((long)Math.Round(value * koef));

						array[i] = v;
						helper.Add(v);
					}
				}
				else
					digits = -1;
			}
			catch (OverflowException)
			{
				digits = -1;
			}

			writer.Write((sbyte)digits);
			if (digits >= 0)
				DeltaCompression.Compress(writer, array, 0, count, helper);
			else
			{
				for (var i = 0; i < count; i++)
					writer.Write(values(i, state));
			}
		}

		public void Load(BinaryReader reader, Action<int, object, decimal> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid DecimalIndexerPersist version.");

			int digits = reader.ReadSByte();

			if (digits >= 0)
			{
				var koef = Math.Pow(10, digits);
				DeltaCompression.Decompress(reader, (idx, o, val) => values(idx, o, (decimal)Math.Round(val / koef, digits)), count, state);
			}
			else //native read
			{
				for (var i = 0; i < count; i++)
					values(i, state, reader.ReadDecimal());
			}
		}

		#endregion
	}
}
